#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <http://www.zhengzhong.net/>
#
# Created on 2008-08-15.
# $Id$
#

import logging

from google.appengine.ext import db
from google.appengine.api import users

#---------------------------------------------------------------------------------------------------

class RestoTag(db.Model):
    """Entity class for a tag on restaurant.
    """
    
    name = db.StringProperty(required=True)
    count = db.IntegerProperty(default=0)
    
    def __str__(self):
        return self.name
    
    def __unicode__(self):
        return self.name
    
    @staticmethod
    def update_tag_count(tag_name, delta):
        tag = RestoTag.gql('WHERE name=:name', name=tag_name).get()
        if not tag and delta > 0:
            tag = RestoTag(name=tag_name, count=delta)
            tag.put()
        elif tag:
            tag.count += delta
            if tag.count <= 0:
                tag.delete()
            else:
                tag.put()

#---------------------------------------------------------------------------------------------------

class Resto(db.Model):

    FULL_STAR = 'full'
    HALF_STAR = 'half'
    NONE_STAR = 'none'
    
    # Basic information.
    uid = db.StringProperty(required=True)
    name = db.StringProperty()
    chinese_name = db.StringProperty()
    address = db.PostalAddressProperty(required=True)
    post_code = db.StringProperty(required=True)
    city = db.StringProperty(required=True)
    website = db.LinkProperty()
    email = db.EmailProperty()
    tel_1 = db.PhoneNumberProperty()
    tel_2 = db.PhoneNumberProperty()
    
    description = db.TextProperty()
    photo_type = db.StringProperty()
    photo_data = db.BlobProperty()
    tag_list = db.StringListProperty(default=[])
    best_dish_list = db.StringListProperty(default=[])
    
    # Geo-location for Google Maps.
    latitude = db.FloatProperty()
    longitude = db.FloatProperty()
    
    # Ratings and comments.
    ratings = db.IntegerProperty(default=0)   # Number of ratings.
    average = db.FloatProperty(default=0.0)   # Average rating, calculated before save.
    cuisine = db.FloatProperty(default=0.0)
    economy = db.FloatProperty(default=0.0)
    service = db.FloatProperty(default=0.0)
    comments = db.IntegerProperty(default=0)  # Number of comments.
    
    # Meta information.
    submitter = db.UserProperty()
    submit_date = db.DateTimeProperty(auto_now_add=True)
    manager_list = db.ListProperty(users.User, default=[])
    
    def __setattr__(self, name, value):
        # Strip white spaces for the following attributes.
        STRIP_WHITE_SPACES = (
            'uid',
            'name',
            'chinese_name',
            'address',
            'post_code',
            'city',
            'website',
            'email',
            'tel_1',
            'tel_2',
            'description',
        )
        if name in STRIP_WHITE_SPACES:
            if value:
                value = value.strip()
        # Set None instead of blank string for the following attributes.
        SET_NONE_FOR_BLANK = ('website', 'email', 'tel_1', 'tel_2',)
        if name in SET_NONE_FOR_BLANK:
            if not value or len(value.strip()) == 0:
                value = None
        super(Resto, self).__setattr__(name, value)
    
    def names(self):
        name_list = []
        if self.chinese_name:
            name_list.append(self.chinese_name)
        if self.name:
            name_list.append(self.name)
        if name_list:
            return ' / '.join(name_list)
        else:
            return None
    
    def tels(self):
        tel_list = []
        if self.tel_1:
            tel_list.append(self.tel_1)
        if self.tel_2:
            tel_list.append(self.tel_2)
        if tel_list:
            return ' / '.join(tel_list)
        else:
            return None
    
    def tags(self):
        return ','.join(self.tag_list)
    
    def best_dishes(self):
        return '\n'.join(self.best_dish_list)
    
    def __rating_as_stars(self, value):
        value_list = []
        for i in range(0, 5):
            if value < i + 0.3:
                value_list.append(Resto.NONE_STAR)
            elif value < i + 0.8:
                value_list.append(Resto.HALF_STAR)
            else:
                value_list.append(Resto.FULL_STAR)
        return value_list
    
    def average_as_stars(self):
        return self.__rating_as_stars(self.average)
    
    def cuisine_as_stars(self):
        return self.__rating_as_stars(self.cuisine)
    
    def economy_as_stars(self):
        return self.__rating_as_stars(self.economy)
    
    def service_as_stars(self):
        return self.__rating_as_stars(self.service)
    
    def __rating_as_str(self, value):
        if value >= 4.0:
            return 'Excellent'
        elif value >= 3.0:
            return 'Pretty good'
        elif value >= 2.0:
            return 'Just so so'
        elif value >= 1.0:
            return 'Quite bad'
        else:
            return 'Very bad'
    
    def average_as_str(self):
        return self.__rating_as_str(self.average)
    
    def cuisine_as_str(self):
        return self.__rating_as_str(self.cuisine)
    
    def economy_as_str(self):
        return self.__rating_as_str(self.economy)
    
    def service_as_str(self):
        return self.__rating_as_str(self.service)
    
    def add_rating(self, cuisine, economy, service):
        # Check rating range.
        if cuisine < 1 or cuisine > 5:
            raise ValueError('Invalid cuisine rating: %d' % cuisine)
        if economy < 1 or economy > 5:
            raise ValueError('Invalid economy rating: %d' % economy)
        if service < 1 or service > 5:
            raise ValueError('Invalid service rating: %d' % service)
        # Update rating attributes.
        self.cuisine = (self.cuisine * self.ratings + cuisine) / (self.ratings + 1)
        self.economy = (self.economy * self.ratings + economy) / (self.ratings + 1)
        self.service = (self.service * self.ratings + service) / (self.ratings + 1)
        self.ratings += 1
    
    def is_manageable(self):
        user = users.get_current_user()
        return user and user in self.manager_list
    
    def put(self):
        # Ensure that manager_list is not empty.
        if not self.manager_list:
            self.manager_list = [self.submitter,]
        # Ensure that city is in lower cases.
        if self.city:
            self.city = self.city.lower()
        # Ensure that tags are in lower cases, and there are no duplicated entries.
        checked_tag_list = []
        for tag in self.tag_list:
            tag = tag.strip().lower()
            if tag and tag not in checked_tag_list:
                checked_tag_list.append(tag)
        self.tag_list = checked_tag_list
        # Ensure that best dishes are in lower cases, and there are no duplicated entries.
        checked_best_dish_list = []
        for best_dish in self.best_dish_list:
            best_dish = best_dish.strip().lower()
            if best_dish and best_dish not in checked_best_dish_list:
                checked_best_dish_list.append(best_dish)
        self.best_dish_list = checked_best_dish_list
        # Calculate the average rating of this resto.
        self.average = (self.cuisine * 2 + self.economy + self.service) / 4.0
        # Update tag counters.
        if self.is_saved():
            original_tag_list = Resto.get(self.key()).tag_list
        else:
            original_tag_list = []
        for old_tag in original_tag_list:
            if not old_tag in self.tag_list:
                RestoTag.update_tag_count(old_tag, -1)
        for new_tag in self.tag_list:
            if not new_tag in original_tag_list:
                RestoTag.update_tag_count(new_tag, 1)
        # Put the entity to datastore.
        is_new = not self.is_saved()
        key = super(Resto, self).put()
        if is_new:
            logging.info('New Resto "%s" inserted to datastore.' % self.name)
        else:
            logging.info('Resto "%s" updated in datastore.' % self.name)
        return key
    
    
    
    
    
    
    
    
    
    
    def is_current_user_editor(self):
        user = users.get_current_user()
        return user in self.editors or user in self.managers
    
    def is_current_user_manager(self):
        user = users.get_current_user()
        return user in self.managers
    
    def set_geo_location(self, latitude, longitude):
        self.latitude  = latitude
        self.longitude = longitude
    
    def has_geo_location(self):
        return self.latitude is not None and self.longitude is not None
    
    def add_rating(self, cuisine, economy, service):
        if cuisine < 1 or cuisine > 5:
            raise ValueError("Invalid cuisine rating: %d" % cuisine)
        if economy < 1 or economy > 5:
            raise ValueError("Invalid economy rating: %d" % economy)
        if service < 1 or service > 5:
            raise ValueError("Invalid service rating: %d" % service)
        self.cuisine = (self.cuisine * self.ratings + cuisine) / (self.ratings + 1)
        self.economy = (self.economy * self.ratings + economy) / (self.ratings + 1)
        self.service = (self.service * self.ratings + service) / (self.ratings + 1)
        self.average = (self.cuisine + self.economy + self.service) / 3.0
        self.ratings += 1
    
    def average_int(self):
        return self.__rating_int(self.average)
    
    def cuisine_int(self):
        return self.__rating_int(self.cuisine)
    
    def economy_int(self):
        return self.__rating_int(self.economy)
    
    def service_int(self):
        return self.__rating_int(self.service)
    
    def __rating_int(self, rating):
        if rating < 1.1:
            return 1
        elif rating < 2.1:
            return 2
        elif rating < 3.1:
            return 3
        elif rating < 4.1:
            return 4
        else:
            return 5
    
    def average_stars(self):
        return self.__rating_stars(self.average)
    
    def cuisine_stars(self):
        return self.__rating_stars(self.cuisine)
    
    def economy_stars(self):
        return self.__rating_stars(self.economy)
    
    def service_stars(self):
        return self.__rating_stars(self.service)
    
    def __rating_stars(self, rating):
        stars = []
        for i in range(0, 5):
            if rating >= i + 0.6:
                stars.append(Resto.FULL_STAR)
            elif rating >= i + 0.1:
                stars.append(Resto.HALF_STAR)
            else:
                stars.append(Resto.EMPTY_STAR)
        return stars
    

#---------------------------------------------------------------------------------------------------

class RestoComment(db.Model):
    """This class represents a comment on a resto. For each comment, the parent entity is the resto
    on which this comment is made.
    """
    
    content = db.TextProperty()
    author = db.UserProperty(required=True)
    post_date = db.DateTimeProperty(auto_now_add=True)
    
    def put(self):
        is_new = not self.is_saved()
        key = super(RestoComment, self).put()
        if is_new:
            self.parent().comments += 1
            self.parent().put()
        return key

    def delete(self):
        if self.is_saved() and self.parent().comments > 0:
            self.parent().comments -= 1
            self.parent().put()
        super(RestoComment, self).delete()

#---------------------------------------------------------------------------------------------------

class RestoRating(db.Model):
    """This class represents a rating on a resto. For each rating, the parent entity is the resto
    on which this rating is made.
    """
    
    cuisine = db.IntegerProperty(required=True)
    economy = db.IntegerProperty(required=True)
    service = db.IntegerProperty(required=True)
    user = db.UserProperty(required=True)
    ip_address = db.StringProperty(required=True)
    rate_date = db.DateProperty(auto_now_add=True)
    
    def average(self):
        return (self.cuisine + self.economy + self.service) / 3.0






